﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;


namespace advadev.Windows.Graph
{

    /// <summary>
    /// Represents a graph container
    /// </summary>
    public class GraphContainer : Grid
    {

        /// <summary>for internal use</summary>
        private List<UIElement> _layerElements = new List<UIElement>();


        #region attached properties
        static public readonly DependencyProperty IsLayerWithComplexGeometryProperty = DependencyProperty.RegisterAttached("IsLayerWithComplexGeometry", typeof(bool), typeof(GraphContainer), new UIPropertyMetadata(false));
        static public bool GetIsLayerWithComplexGeometry(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsLayerWithComplexGeometryProperty);
        }
        static public void SetIsLayerWithComplexGeometry(DependencyObject obj, bool value)
        {
            obj.SetValue(IsLayerWithComplexGeometryProperty, value);
        }

        static public readonly DependencyProperty LayerIndexProperty = DependencyProperty.RegisterAttached("LayerIndex", typeof(int), typeof(GraphContainer), new UIPropertyMetadata(0, onLayerIndexChanged));
        static public int GetLayerIndex(DependencyObject obj)
        {
            return (int)obj.GetValue(LayerIndexProperty);
        }
        static public void SetLayerIndex(DependencyObject obj, int value)
        {
            obj.SetValue(LayerIndexProperty, value);
        }

        static public readonly DependencyProperty HorizontalStartNodeAlignmentProperty = DependencyProperty.RegisterAttached("HorizontalStartNodeAlignment", typeof(HorizontalAlignment), typeof(GraphContainer), new UIPropertyMetadata(HorizontalAlignment.Right));
        static public HorizontalAlignment GetHorizontalStartNodeAlignment(DependencyObject obj)
        {
            return (HorizontalAlignment)obj.GetValue(HorizontalStartNodeAlignmentProperty);
        }
        static public void SetHorizontalStartNodeAlignment(DependencyObject obj, HorizontalAlignment value)
        {
            obj.SetValue(HorizontalStartNodeAlignmentProperty, value);
        }

        static public readonly DependencyProperty HorizontalEndNodeAlignmentProperty = DependencyProperty.RegisterAttached("HorizontalEndNodeAlignment", typeof(HorizontalAlignment), typeof(GraphContainer), new UIPropertyMetadata(HorizontalAlignment.Left));
        static public HorizontalAlignment GetHorizontalEndNodeAlignment(DependencyObject obj)
        {
            return (HorizontalAlignment)obj.GetValue(HorizontalEndNodeAlignmentProperty);
        }
        static public void SetHorizontalEndNodeAlignment(DependencyObject obj, HorizontalAlignment value)
        {
            obj.SetValue(HorizontalEndNodeAlignmentProperty, value);
        }

        static public readonly DependencyProperty VerticalStartNodeAlignmentProperty = DependencyProperty.RegisterAttached("VerticalStartNodeAlignment", typeof(VerticalAlignment), typeof(GraphContainer), new UIPropertyMetadata(VerticalAlignment.Center));
        static public VerticalAlignment GetVerticalStartNodeAlignment(DependencyObject obj)
        {
            return (VerticalAlignment)obj.GetValue(VerticalStartNodeAlignmentProperty);
        }
        static public void SetVerticalStartNodeAlignment(DependencyObject obj, VerticalAlignment value)
        {
            obj.SetValue(VerticalStartNodeAlignmentProperty, value);
        }

        static public readonly DependencyProperty VerticalEndNodeAlignmentProperty = DependencyProperty.RegisterAttached("VerticalEndNodeAlignment", typeof(VerticalAlignment), typeof(GraphContainer), new UIPropertyMetadata(VerticalAlignment.Center));
        static public VerticalAlignment GetVerticalEndNodeAlignment(DependencyObject obj)
        {
            return (VerticalAlignment)obj.GetValue(VerticalEndNodeAlignmentProperty);
        }
        static public void SetVerticalEndNodeAlignment(DependencyObject obj, VerticalAlignment value)
        {
            obj.SetValue(VerticalEndNodeAlignmentProperty, value);
        }

        static public readonly DependencyProperty LineDirectionProperty = DependencyProperty.RegisterAttached("LineDirection", typeof(GraphLineDirection?), typeof(GraphContainer), new UIPropertyMetadata(null));
        static public GraphLineDirection? GetLineDirection(DependencyObject obj)
        {
            return (GraphLineDirection?)obj.GetValue(LineDirectionProperty);
        }
        static public void SetLineDirection(DependencyObject obj, GraphLineDirection? value)
        {
            obj.SetValue(LineDirectionProperty, value);
        }

        static public readonly DependencyProperty LineOffsetProperty = DependencyProperty.RegisterAttached("LineOffset", typeof(double?), typeof(GraphContainer), new UIPropertyMetadata(null));
        static public double? GetLineOffset(DependencyObject obj)
        {
            return (double?)obj.GetValue(LineOffsetProperty);
        }
        static public void SetLineOffset(DependencyObject obj, double? value)
        {
            obj.SetValue(LineOffsetProperty, value);
        }

        static public readonly DependencyProperty GraphNodeLayerIndexProperty = DependencyProperty.RegisterAttached("GraphNodeLayerIndex", typeof(int), typeof(GraphContainer), new UIPropertyMetadata(0));
        static public int GetGraphNodeLayerIndex(DependencyObject obj)
        {
            return (int)obj.GetValue(GraphNodeLayerIndexProperty);
        }
        static public void SetGraphNodeLayerIndex(DependencyObject obj, int value)
        {
            obj.SetValue(GraphNodeLayerIndexProperty, value);
        }

        static public readonly DependencyProperty GraphNodeIndexProperty = DependencyProperty.RegisterAttached("GraphNodeIndex", typeof(int), typeof(GraphContainer), new UIPropertyMetadata(0));
        static public int GetGraphNodeIndex(DependencyObject obj)
        {
            return (int)obj.GetValue(GraphNodeIndexProperty);
        }
        static public void SetGraphNodeIndex(DependencyObject obj, int value)
        {
            obj.SetValue(GraphNodeIndexProperty, value);
        }

        static public readonly DependencyProperty GraphNameProperty = DependencyProperty.RegisterAttached("GraphName", typeof(string), typeof(GraphContainer), new UIPropertyMetadata(string.Empty, onGraphNameChanged));
        static public string GetGraphName(DependencyObject obj)
        {
            return (string)obj.GetValue(GraphNameProperty);
        }
        static public void SetGraphName(DependencyObject obj, string value)
        {
            obj.SetValue(GraphNameProperty, value);
        }

        static internal readonly DependencyProperty ParentGraphContainerProperty = DependencyProperty.RegisterAttached("ParentGraphContainer", typeof(GraphContainer), typeof(GraphContainer), new UIPropertyMetadata(null));
        static internal GraphContainer GetParentGraphContainer(DependencyObject obj)
        {
            return (GraphContainer)obj.GetValue(ParentGraphContainerProperty);
        }
        static internal void SetParentGraphContainer(DependencyObject obj, GraphContainer value)
        {
            obj.SetValue(ParentGraphContainerProperty, value);
        }
        #endregion attached properties


        /// <summary>
        /// Handles the "Changed" event of the "GraphName" attached property
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        static private void onGraphNameChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            FrameworkElement nodeElement = sender as FrameworkElement;
            if (nodeElement != null)
            {
                if (nodeElement.IsLoaded)
                {
                    GraphContainer graphContainer = findParent<GraphContainer>(nodeElement);
                    if (graphContainer != null && args != null)
                    {
                        SetParentGraphContainer(nodeElement, graphContainer);
                        graphContainer.onGraphNameChanged(nodeElement, args.OldValue as string, args.NewValue as string);
                    }
                    nodeElement.Unloaded -= new RoutedEventHandler(nodeElement_Unloaded);
                    nodeElement.Loaded -= new RoutedEventHandler(nodeElement_Loaded);
                    nodeElement.Unloaded += new RoutedEventHandler(nodeElement_Unloaded);
                }
                else
                {
                    nodeElement.Unloaded -= new RoutedEventHandler(nodeElement_Unloaded);
                    nodeElement.Loaded -= new RoutedEventHandler(nodeElement_Loaded);
                    nodeElement.Loaded += new RoutedEventHandler(nodeElement_Loaded);
                }
            }
        }

        /// <summary>
        /// Handles the "Loaded" event of a node element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        static private void nodeElement_Loaded(object sender, RoutedEventArgs args)
        {
            FrameworkElement nodeElement = sender as FrameworkElement;
            if (nodeElement != null)
            {
                GraphContainer graphContainer = findParent<GraphContainer>(nodeElement);
                if (graphContainer != null && args != null)
                {
                    SetParentGraphContainer(nodeElement, graphContainer);
                    graphContainer.onGraphNameChanged(nodeElement, null, GetGraphName(nodeElement));
                }
                nodeElement.Unloaded -= new RoutedEventHandler(nodeElement_Unloaded);
                nodeElement.Loaded -= new RoutedEventHandler(nodeElement_Loaded);
                nodeElement.Unloaded += new RoutedEventHandler(nodeElement_Unloaded);
            }
        }

        /// <summary>
        /// Handles the "Unloaded" event of a node element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        static private void nodeElement_Unloaded(object sender, RoutedEventArgs args)
        {
            FrameworkElement nodeElement = sender as FrameworkElement;
            if (nodeElement != null)
            {
                GraphContainer graphContainer = GetParentGraphContainer(nodeElement);
                if (graphContainer != null && args != null)
                {
                    graphContainer.onGraphNameChanged(nodeElement, GetGraphName(nodeElement), null);
                }
                nodeElement.Unloaded -= new RoutedEventHandler(nodeElement_Unloaded);
                nodeElement.Loaded -= new RoutedEventHandler(nodeElement_Loaded);
                nodeElement.Loaded += new RoutedEventHandler(nodeElement_Loaded);
            }
        }

        /// <summary>
        /// Handles the "Changed" event of the "LayerIndex" attached property
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        static private void onLayerIndexChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            FrameworkElement layerElement = sender as FrameworkElement;
            if (layerElement != null)
            {
                if (layerElement.IsLoaded)
                {
                    GraphContainer graphContainer = findParent<GraphContainer>(layerElement);
                    if (graphContainer != null && args != null)
                    {
                        SetParentGraphContainer(layerElement, graphContainer);
                        graphContainer.onLayerIndexChanged(layerElement, (int)args.OldValue, (int)args.NewValue);
                    }
                    layerElement.Unloaded -= new RoutedEventHandler(layerElement_Unloaded);
                    layerElement.Loaded -= new RoutedEventHandler(layerElement_Loaded);
                    layerElement.Unloaded += new RoutedEventHandler(layerElement_Unloaded);
                }
                else
                {
                    layerElement.Unloaded -= new RoutedEventHandler(layerElement_Unloaded);
                    layerElement.Loaded -= new RoutedEventHandler(layerElement_Loaded);
                    layerElement.Loaded += new RoutedEventHandler(layerElement_Loaded);
                }
            }
        }

        /// <summary>
        /// Handles the "Loaded" event of a layer element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        static private void layerElement_Loaded(object sender, RoutedEventArgs args)
        {
            FrameworkElement layerElement = sender as FrameworkElement;
            if (layerElement != null)
            {
                GraphContainer graphContainer = findParent<GraphContainer>(layerElement);
                if (graphContainer != null && args != null)
                {
                    SetParentGraphContainer(layerElement, graphContainer);
                    graphContainer.onLayerIndexChanged(layerElement, 0, GetLayerIndex(layerElement));
                }
                else
                {
                    if (!layerElement.IsVisible)
                    {
                        layerElement.IsVisibleChanged += new DependencyPropertyChangedEventHandler(layerElement_IsVisibleChanged);
                    }
                }
                layerElement.Unloaded -= new RoutedEventHandler(layerElement_Unloaded);
                layerElement.Loaded -= new RoutedEventHandler(layerElement_Loaded);
                layerElement.Unloaded += new RoutedEventHandler(layerElement_Unloaded);
            }
        }

        /// <summary>
        /// Handles the "IsVisibleChanged" event of a layer element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private static void layerElement_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            FrameworkElement layerElement = sender as FrameworkElement;
            if (layerElement != null)
            {
                layerElement.IsVisibleChanged -= new DependencyPropertyChangedEventHandler(layerElement_IsVisibleChanged);
                GraphContainer graphContainer = findParent<GraphContainer>(layerElement);
                if (graphContainer != null && args != null)
                {
                    SetParentGraphContainer(layerElement, graphContainer);
                    graphContainer.onLayerIndexChanged(layerElement, 0, GetLayerIndex(layerElement));
                }
            }
        }

        /// <summary>
        /// Handles the "Unloaded" event of a layer element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        static private void layerElement_Unloaded(object sender, RoutedEventArgs args)
        {
            FrameworkElement layerElement = sender as FrameworkElement;
            if (layerElement != null)
            {
                GraphContainer graphContainer = GetParentGraphContainer(layerElement);
                if (graphContainer != null && args != null)
                {
                    graphContainer.onLayerIndexChanged(layerElement, GetLayerIndex(layerElement), 0);
                }
                layerElement.Unloaded -= new RoutedEventHandler(layerElement_Unloaded);
                layerElement.Loaded -= new RoutedEventHandler(layerElement_Loaded);
                layerElement.Loaded += new RoutedEventHandler(layerElement_Loaded);
            }
        }

        /// <summary>
        /// Finds the parent element of the given type for the given child
        /// element
        /// </summary>
        /// <typeparam name="T">
        /// Type of the parent to find
        /// </typeparam>
        /// <param name="childElement">
        /// Child element
        /// </param>
        /// <returns>
        /// Parent element or null
        /// </returns>
        static private T findParent<T>(DependencyObject childElement) where T : DependencyObject
        {
            T visualParent = findVisualParent<T>(childElement);
            if (visualParent != null)
            {
                return visualParent;
            }
            T logicalParent = findLogicalParent<T>(childElement);
            if (logicalParent != null)
            {
                return logicalParent;
            }
            return null;
        }

        /// <summary>
        /// Finds the parent visual element of the given type for the given child
        /// element
        /// </summary>
        /// <typeparam name="T">
        /// Type of the parent to find
        /// </typeparam>
        /// <param name="childElement">
        /// Child element
        /// </param>
        /// <returns>
        /// Parent element or null
        /// </returns>
        static private T findVisualParent<T>(DependencyObject childElement) where T : DependencyObject
        {
            DependencyObject visualParent = VisualTreeHelper.GetParent(childElement);
            if (visualParent == null)
            {
                return null;
            }
            T parent = visualParent as T;
            if (parent != null)
            {
                return parent;
            }
            else
            {
                return findVisualParent<T>(visualParent);
            }
        }

        /// <summary>
        /// Finds the parent logical element of the given type for the given
        /// child element
        /// </summary>
        /// <typeparam name="T">
        /// Type of the parent to find
        /// </typeparam>
        /// <param name="childElement">
        /// Child element
        /// </param>
        /// <returns>
        /// Parent element or null
        /// </returns>
        static private T findLogicalParent<T>(DependencyObject childElement) where T : DependencyObject
        {
            DependencyObject logicalParent = LogicalTreeHelper.GetParent(childElement);
            if (logicalParent == null)
            {
                return null;
            }
            T parent = logicalParent as T;
            if (parent != null)
            {
                return parent;
            }
            else
            {
                return findLogicalParent<T>(logicalParent);
            }
        }

        /// <summary>
        /// Handles the "Changed" event of the "GraphName" attached property of
        /// the given node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        /// <param name="oldGraphName">
        /// Old graph name
        /// </param>
        /// <param name="newGraphName">
        /// New graph name
        /// </param>
        private void onGraphNameChanged(FrameworkElement nodeElement, string oldGraphName, string newGraphName)
        {
            if (nodeElement != null)
            {
                if (!string.IsNullOrEmpty(oldGraphName))
                {
                    Graph graph = findGraph(oldGraphName);
                    if (graph != null)
                    {
                        graph.RemoveNodeElement(nodeElement);
                        if (!graph.HasNodeElements)
                        {
                            InternalChildren.Remove(graph);
                        }
                    }
                }
                if (!string.IsNullOrEmpty(newGraphName))
                {
                    Graph graph = findGraph(newGraphName);
                    if (graph != null)
                    {
                        graph.AddNodeElement(nodeElement);
                    }
                    else
                    {
                        graph = new Graph();
                        graph.GraphName = newGraphName;
                        graph.AddNodeElement(nodeElement);
                        foreach (UIElement layerElement in _layerElements)
                        {
                            graph.AddLayerElement(layerElement);
                        }
                        InternalChildren.Add(graph);
                    }
                }
            }
        }

        /// <summary>
        /// Handles the "Changed" event of the "LayerIndex" attached property of
        /// the given layer element
        /// </summary>
        /// <param name="layerElement">
        /// Layer element
        /// </param>
        /// <param name="oldLayerIndex">
        /// Old layer index
        /// </param>
        /// <param name="newLayerIndex">
        /// New layer index
        /// </param>
        private void onLayerIndexChanged(FrameworkElement layerElement, int oldLayerIndex, int newLayerIndex)
        {
            if (layerElement != null)
            {
                if (oldLayerIndex != 0)
                {
                    if (_layerElements.Contains(layerElement))
                    {
                        foreach (UIElement uiElement in InternalChildren)
                        {
                            Graph graph = uiElement as Graph;
                            if (graph != null)
                            {
                                graph.RemoveLayerElement(layerElement);
                            }
                        }
                        _layerElements.Remove(layerElement);
                    }
                }
                if (newLayerIndex != 0)
                {
                    if (!_layerElements.Contains(layerElement))
                    {
                        _layerElements.Add(layerElement);
                        foreach (UIElement uiElement in InternalChildren)
                        {
                            Graph graph = uiElement as Graph;
                            if (graph != null)
                            {
                                graph.AddLayerElement(layerElement);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Finds the graph with the given name
        /// </summary>
        /// <param name="graphName">
        /// Graph name
        /// </param>
        /// <returns>
        /// Graph with the given name or null
        /// </returns>
        private Graph findGraph(string graphName)
        {
            foreach (UIElement uiElement in InternalChildren)
            {
                Graph graph = uiElement as Graph;
                if (graph != null && graph.GraphName == graphName)
                {
                    return graph;
                }
            }
            return null;
        }

    }

}
